package cn.iocoder.boot;

import java.util.*;
import java.util.stream.Collectors;

public class CollaborativeFilteringRecommender {

    // 用户-物品评分矩阵
    private Map<Integer, Map<Integer, Double>> userItemMatrix;

    public CollaborativeFilteringRecommender(Map<Integer, Map<Integer, Double>> userItemMatrix) {
        this.userItemMatrix = userItemMatrix;
    }

    // 计算两个用户的余弦相似度（用于UserCF）
    public static double cosineSimilarityUser(Map<Integer, Double> user1Ratings, Map<Integer, Double> user2Ratings) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        Set<Integer> commonItems = new HashSet<>(user1Ratings.keySet());
        commonItems.retainAll(user2Ratings.keySet());

        for (int item : commonItems) {
            double r1 = user1Ratings.get(item);
            double r2 = user2Ratings.get(item);
            dotProduct += r1 * r2;
            norm1 += Math.pow(r1, 2);
            norm2 += Math.pow(r2, 2);
        }

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    // 计算两个物品的余弦相似度（用于ItemCF）
    public static double cosineSimilarityItem(Map<Integer, Double> item1Ratings, Map<Integer, Double> item2Ratings) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        Set<Integer> commonUsers = new HashSet<>(item1Ratings.keySet());
        commonUsers.retainAll(item2Ratings.keySet());

        for (int user : commonUsers) {
            double r1 = item1Ratings.get(user);
            double r2 = item2Ratings.get(user);
            dotProduct += r1 * r2;
            norm1 += Math.pow(r1, 2);
            norm2 += Math.pow(r2, 2);
        }

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    // UserCF：计算目标用户与其他用户的相似度
    public Map<Integer, Double> computeUserSimilarities(int targetUser) {
        Map<Integer, Double> similarities = new HashMap<>();
        Map<Integer, Double> targetRatings = userItemMatrix.get(targetUser);

        for (int user : userItemMatrix.keySet()) {
            if (user == targetUser) continue;

            double similarity = cosineSimilarityUser(targetRatings, userItemMatrix.get(user));
            similarities.put(user, similarity);
        }

        return similarities;
    }

    // UserCF：获取前 K 个最相似的用户
    public List<Integer> getTopKSimilarUsers(int targetUser, int k) {
        Map<Integer, Double> similarities = computeUserSimilarities(targetUser);
        List<Map.Entry<Integer, Double>> similarityEntries = new ArrayList<>(similarities.entrySet());

        similarityEntries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));

        return similarityEntries.stream()
                .limit(k)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    // UserCF：为目标用户生成推荐物品
    public Set<Integer> generateRecommendationsUserCF(int targetUser, int k) {
        List<Integer> topKUsers = getTopKSimilarUsers(targetUser, k);
        Set<Integer> recommendedItems = new HashSet<>();

        for (int user : topKUsers) {
            Map<Integer, Double> userRatings = userItemMatrix.get(user);
            for (int item : userRatings.keySet()) {
                if (!userItemMatrix.get(targetUser).containsKey(item)) {
                    recommendedItems.add(item);
                }
            }
        }

        return recommendedItems;
    }

    // ItemCF：构建物品相似度矩阵
    public Map<Integer, Map<Integer, Double>> buildItemSimilarityMatrix() {
        Map<Integer, Map<Integer, Double>> itemSimilarity = new HashMap<>();

        Set<Integer> allItems = new HashSet<>();
        for (Map<Integer, Double> itemRatings : userItemMatrix.values()) {
            allItems.addAll(itemRatings.keySet());
        }

        for (int item1 : allItems) {
            Map<Integer, Double> item1Ratings = new HashMap<>();
            for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
                int user = entry.getKey();
                double rating = entry.getValue().getOrDefault(item1, 0.0);
                item1Ratings.put(user, rating);
            }

            itemSimilarity.put(item1, new HashMap<>());
            for (int item2 : allItems) {
                if (item1 != item2) {
                    Map<Integer, Double> item2Ratings = new HashMap<>();
                    for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
                        int user = entry.getKey();
                        double rating = entry.getValue().getOrDefault(item2, 0.0);
                        item2Ratings.put(user, rating);
                    }

                    double similarity = cosineSimilarityItem(item1Ratings, item2Ratings);
                    itemSimilarity.get(item1).put(item2, similarity);
                }
            }
        }

        return itemSimilarity;
    }

    // ItemCF：为目标用户生成推荐物品
    public Set<Integer> generateRecommendationsItemCF(int targetUser, int k) {
        Map<Integer, Map<Integer, Double>> itemSimilarity = buildItemSimilarityMatrix();
        Map<Integer, Double> userRatings = userItemMatrix.get(targetUser);
        Set<Integer> recommendedItems = new HashSet<>();

        List<Map.Entry<Integer, Double>> ratedItems = new ArrayList<>(userRatings.entrySet());
        ratedItems.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));
        List<Integer> topKItems = ratedItems.stream().limit(k).map(Map.Entry::getKey).collect(Collectors.toList());

        for (int item : topKItems) {
            Map<Integer, Double> similarItems = itemSimilarity.getOrDefault(item, new HashMap<>());
            if (similarItems.isEmpty()) continue;

            double maxSimilarity = -1;
            int recommendedItem = -1;
            for (Map.Entry<Integer, Double> similarityEntry : similarItems.entrySet()) {
                int similarItem = similarityEntry.getKey();
                double similarity = similarityEntry.getValue();

                if (!userRatings.containsKey(similarItem) && similarity > maxSimilarity) {
                    maxSimilarity = similarity;
                    recommendedItem = similarItem;
                }
            }

            if (recommendedItem != -1) {
                recommendedItems.add(recommendedItem);
            }
        }

        return recommendedItems;
    }

    // 输出UserCF推荐理由
    public void explainRecommendationsUserCF(int targetUser, Set<Integer> recommendedItems, int k) {
        System.out.println("推荐理由 (UserCF)");
        System.out.println("------------------------------------");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendedItems);

        List<Integer> topKUsers = getTopKSimilarUsers(targetUser, k);
        System.out.println("与目标用户最相似的 " + k + " 个用户: " + topKUsers);

        for (int user : topKUsers) {
            Map<Integer, Double> userRatings = userItemMatrix.get(user);
            for (int item : recommendedItems) {
                if (userRatings.containsKey(item)) {
                    System.out.println("用户 " + user + " 对物品 " + item + " 的评分为: " + userRatings.get(item));
                }
            }
        }

        System.out.println("------------------------------------");
    }

    // 输出ItemCF推荐理由
    public void explainRecommendationsItemCF(int targetUser, Set<Integer> recommendedItems) {
        System.out.println("推荐理由 (ItemCF)");
        System.out.println("------------------------------------");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendedItems);

        for (int item : recommendedItems) {
            System.out.println("推荐物品 " + item + " 的原因：");
            System.out.println("该物品与目标用户评分较高的物品具有较高的相似度。");
            System.out.println("相似度计算基于余弦相似度算法，通过比较物品在用户评分向量上的夹角来确定相似性。");
            System.out.println("余弦相似度值越接近1，表示两个物品越相似。");
        }

        System.out.println("------------------------------------");
    }

    public static void main(String[] args) {
        // 示例用户-物品评分矩阵（手动设定数据）
        Map<Integer, Map<Integer, Double>> userItemMatrix = new HashMap<>();

        // 用户1的评分
        Map<Integer, Double> user1Ratings = new HashMap<>();
        user1Ratings.put(101, 4.5);
        user1Ratings.put(102, 3.0);
        user1Ratings.put(103, 4.0);
        userItemMatrix.put(1, user1Ratings);

        // 用户2的评分
        Map<Integer, Double> user2Ratings = new HashMap<>();
        user2Ratings.put(101, 5.0);
        user2Ratings.put(102, 4.0);
        user2Ratings.put(104, 3.5);
        userItemMatrix.put(2, user2Ratings);

        // 用户3的评分
        Map<Integer, Double> user3Ratings = new HashMap<>();
        user3Ratings.put(102, 4.0);
        user3Ratings.put(103, 5.0);
        user3Ratings.put(104, 4.5);
        userItemMatrix.put(3, user3Ratings);

        // 用户4的评分
        Map<Integer, Double> user4Ratings = new HashMap<>();
        user4Ratings.put(101, 3.5);
        user4Ratings.put(103, 4.5);
        user4Ratings.put(104, 5.0);
        userItemMatrix.put(4, user4Ratings);

        // 用户5的评分
        Map<Integer, Double> user5Ratings = new HashMap<>();
        user5Ratings.put(101, 4.0);
        user5Ratings.put(102, 3.5);
        user5Ratings.put(105, 4.5);
        userItemMatrix.put(5, user5Ratings);

        // 用户6的评分
        Map<Integer, Double> user6Ratings = new HashMap<>();
        user6Ratings.put(103, 4.0);
        user6Ratings.put(104, 3.5);
        user6Ratings.put(105, 5.0);
        userItemMatrix.put(6, user6Ratings);

        // 用户7的评分
        Map<Integer, Double> user7Ratings = new HashMap<>();
        user7Ratings.put(101, 4.5);
        user7Ratings.put(102, 4.0);
        user7Ratings.put(106, 3.5);
        userItemMatrix.put(7, user7Ratings);

        // 用户8的评分
        Map<Integer, Double> user8Ratings = new HashMap<>();
        user8Ratings.put(102, 3.5);
        user8Ratings.put(103, 4.0);
        user8Ratings.put(106, 4.5);
        userItemMatrix.put(8, user8Ratings);

        // 用户9的评分
        Map<Integer, Double> user9Ratings = new HashMap<>();
        user9Ratings.put(104, 4.0);
        user9Ratings.put(105, 3.5);
        user9Ratings.put(106, 4.0);
        userItemMatrix.put(9, user9Ratings);

        // 用户10的评分
        Map<Integer, Double> user10Ratings = new HashMap<>();
        user10Ratings.put(101, 3.0);
        user10Ratings.put(105, 4.0);
        user10Ratings.put(106, 3.0);
        userItemMatrix.put(10, user10Ratings);

        // 用户11的评分
        Map<Integer, Double> user11Ratings = new HashMap<>();
        user11Ratings.put(103, 3.5);
        user11Ratings.put(104, 4.0);
        user11Ratings.put(107, 4.5);
        userItemMatrix.put(11, user11Ratings);

        // 用户12的评分
        Map<Integer, Double> user12Ratings = new HashMap<>();
        user12Ratings.put(105, 3.0);
        user12Ratings.put(106, 3.5);
        user12Ratings.put(107, 4.0);
        userItemMatrix.put(12, user12Ratings);

        CollaborativeFilteringRecommender recommender = new CollaborativeFilteringRecommender(userItemMatrix);

        // UserCF：为目标用户生成推荐物品
        int targetUser = 1;
        int k = 2;
        Set<Integer> recommendationsUserCF = recommender.generateRecommendationsUserCF(targetUser, k);

        // 输出UserCF推荐结果
        System.out.println("====================================");
        System.out.println("推荐结果 (UserCF)");
        System.out.println("====================================");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendationsUserCF);
        System.out.println("------------------------------------");

        // 输出UserCF推荐理由
        recommender.explainRecommendationsUserCF(targetUser, recommendationsUserCF, k);

        // ItemCF：为目标用户生成推荐物品
        Set<Integer> recommendationsItemCF = recommender.generateRecommendationsItemCF(targetUser, k);

        // 输出ItemCF推荐结果
        System.out.println("====================================");
        System.out.println("推荐结果 (ItemCF)");
        System.out.println("====================================");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendationsItemCF);
        System.out.println("------------------------------------");

        // 输出ItemCF推荐理由
        recommender.explainRecommendationsItemCF(targetUser, recommendationsItemCF);
    }
}